/*
* INSANE - Interactive Structural Analysis Environment
*
* Copyright (C) 2003-2004
* Universidade Federal de Minas Gerais
* Escola de Engenharia
* Departamento de Engenharia de Estruturas
*
* Author's email : insane@dees.ufmg.br
* Author's Website : http://www.dees.ufmg.br/insane
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*
*/
package br.ufmg.dees.insane.model.xfemModel.enrichmentItem;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.ListIterator;

import br.ufmg.dees.insane.model.femModel.elementNode.ElementNode;
import br.ufmg.dees.insane.model.xfemModel.XFemModel;
import br.ufmg.dees.insane.model.xfemModel.crackGrowthDirectionLaw.CrackGrowthDirectionLaw;
import br.ufmg.dees.insane.model.xfemModel.crackGrowthDirectionLaw.NonLocalPrincipalStressDirection;
import br.ufmg.dees.insane.model.xfemModel.crackGrowthIncrementLaw.BoundaryElementIncrement;
import br.ufmg.dees.insane.model.xfemModel.crackGrowthIncrementLaw.CrackGrowthIncrementLaw;
import br.ufmg.dees.insane.model.xfemModel.enrichmentDetector.ElementAroundPointDetect;
import br.ufmg.dees.insane.model.xfemModel.geometryEntity.PiecewiseLinear;
import br.ufmg.dees.insane.model.xfemModel.geometryEntity.Vertex;
import br.ufmg.dees.insane.model.xfemModel.xfemElement.XFEMElement;
import br.ufmg.dees.insane.model.xfemModel.xfemelementNode.XFEMElementNode;
import br.ufmg.dees.insane.util.IPoint3d;
import br.ufmg.dees.insane.util.IVector;
import br.ufmg.dees.insane.util.xfem.Line;
import br.ufmg.dees.insane.util.xfem.Segment;

/**
 * @author Kelson Wolff
 *
 */
public class CrackTip extends EnrichmentItem {
	
	/**
	 * Comment for <code>serialVersionUID</code>
	 */
	private static final long serialVersionUID = 1L;
	
	/**
	 * 
	 */
	protected Segment tipSegment;
	
	/**
	 * 
	 */
	protected boolean isActive;
	
	/**
	 * 
	 */
	protected CrackInterior myCrackInterior;
	
	/**
	 * The boolean information if this crack tip is on an element edge or not. 
	 * If isOnElementEdge holds true, this crack tip is on element edge. If 
	 * isOnElementEdge holds false, this crack tip is inside an element or on a node.
	 */
	protected boolean isOnElementEdge;
	
	/**
	 * 
	 */
	protected boolean stillInTipElement;
	
	/**
	 * The boolean information if this crack tip is on an node or not. 
	 * If isOnNode holds true, this crack tip is on node. If isOnNode holds
	 * false, this crack tip is inside an element or on element edge.
	 */
	protected boolean isOnNode;

	/**
	 * 
	 */
	public CrackTip() {
		super();
		// TODO Auto-generated constructor stub
		this.myGeometry = new Vertex();
		this.tipSegment = null;
		this.isActive = true;
		this.stillInTipElement = false;
		this.isOnElementEdge = false;
		this.isOnNode = false;
		this.myEnrDetector = new ElementAroundPointDetect();
	}
	
	/**
	 * @return Returns the stillInTipElement.
	 */
	public boolean isStillInTipElement() {
		return stillInTipElement;
	}

	/**
	 * @param stillInTipElement The stillInTipElement to set.
	 */
	public void setStillInTipElement(boolean stillInTipElement) {
		this.stillInTipElement = stillInTipElement;
	}

	/**
	 * Gets the boolean information if this crack tip is on an node or not. 
	 * If isOnNode holds true, this crack tip is on node. If isOnNode holds
	 * false, this crack tip is inside an element or on an element edge.
	 * @return Returns the isOnNode.
	 */
	public boolean isOnNode() {
		return isOnNode;
	}

	/**
	 * Sets the boolean information if this crack tip is on an node or not. 
	 * If isOnNode holds true, this crack tip is on node. If isOnNode holds
	 * false, this crack tip is inside an element or on an element edge.
	 * @param isOnNode The isOnNode to set.
	 */
	public void setOnNode(boolean isOnNode) {
		this.isOnNode = isOnNode;
	}

	/**
	 * @return Returns the isActive.
	 */
	public boolean isActive() {
		return isActive;
	}

	/**
	 * @return Returns the myCrackInterior.
	 */
	public CrackInterior getMyCrackInterior() {
		return myCrackInterior;
	}

	public void computeInteractionIntegral(){
		
	}
	
	/**
	 * @return Returns the tipSegment.
	 */
	public Segment getTipSegment() {
		if(this.tipSegment == null){
			Vertex tip = this.getTip();
			PiecewiseLinear myDead = tip.getMyParent();
			this.tipSegment = myDead.getSegmentContain(tip);
		}
		return tipSegment;
	}

	/**
	 * @param tipSegment The tipSegment to set.
	 */
	public void setTipSegment(Segment tipSegment) {
		this.tipSegment = tipSegment;
	}

	/**
	 * @param isActive The isActive to set.
	 */
	public void setActive(boolean isActive) {
		this.isActive = isActive;
	}

	/**
	 * @param myCrackInterior The myCrackInterior to set.
	 */
	public void setMyCrackInterior(CrackInterior myCrackInterior) {
		this.myCrackInterior = myCrackInterior;
	}
	
	/**
	 * 
	 */
	public boolean giveState(){
		return this.isActive;
	}
	
	/**
	 * 
	 */
	public void kill(){
		this.isActive = false;
	}

	/* (non-Javadoc)
	 * @see br.ufmg.dees.insane.model.xfemModel.enrichmentItem.EnrichmentItem#resolveLinearDependency()
	 */
	@Override
	public void resolveLinearDependency() {
		// TODO Auto-generated method stub

	}

	/* (non-Javadoc)
	 * @see br.ufmg.dees.insane.model.xfemModel.enrichmentItem.EnrichmentItem#updateMyGeometry()
	 */
	@Override
	public void updateMyGeometry(NonLocalPrincipalStressDirection dirLaw, BoundaryElementIncrement incLaw) {
		// TODO Auto-generated method stub
		int lastIndex = this.getInteractedElements().size() - 1;
		XFEMElement currentTipElement = this.getInteractedElements().get(lastIndex);
		IPoint3d currentCrackTipPoint = new IPoint3d(this.getTip().getCoordinates(0),this.getTip().getCoordinates(1));
		
		XFEMElement updatedTipElement = currentTipElement.cloneMyself();
		
		if(this.getTip().isInside(currentTipElement) || this.getTip().isOnEdge(currentTipElement)){
			updatedTipElement = currentTipElement;
		}
		else{
			List<XFEMElement> myNeighbors = currentTipElement.getMyNeighbors();
			ListIterator neighbors = myNeighbors.listIterator();
			while(neighbors.hasNext()){
				XFEMElement currentNeighbor = (XFEMElement) neighbors.next();
				if(this.getTip().isOnEdge(currentNeighbor) || this.getTip().isInside(currentNeighbor)){
					updatedTipElement = currentNeighbor;
				}
			}
		}
		
		IVector normalDirection = dirLaw.getDirection(currentTipElement,currentCrackTipPoint);
		IVector tgDirection1 = new IVector(3);
		tgDirection1.setElement(0,normalDirection.getElement(1));
		tgDirection1.setElement(1,-normalDirection.getElement(0));
		tgDirection1.setElement(2,0.0);
		
		IPoint3d updatedCrackTipPoint = incLaw.getCrackTip(updatedTipElement,tgDirection1);
		
		Vertex updatedTip = new Vertex();
		updatedTip = (Vertex) this.getMyGeometry();
		updatedTip.setCoordinates(updatedCrackTipPoint.getX(),updatedCrackTipPoint.getY());
		this.setMyGeometry(updatedTip);
		
		Segment s = new Segment(currentCrackTipPoint,updatedCrackTipPoint);
		this.setTipSegment(s);
		
		int k = this.getInteractedElements().size() - 1;
		XFEMElement tipE = this.getInteractedElements().get(k);
		if( ((Vertex)this.getMyGeometry()).interactsWith(tipE) ){
			this.setStillInTipElement(true);
		}
		else
			this.setStillInTipElement(false);
	}
	
	@Override
	public void updateMyGeometry(CrackGrowthDirectionLaw dirLaw, CrackGrowthIncrementLaw incLaw) {
		// TODO Auto-generated method stub
		
	}

	/* (non-Javadoc)
	 * @see br.ufmg.dees.insane.model.xfemModel.enrichmentItem.EnrichmentItem#updateEnrichment()
	 */
	@Override
	public void updateEnrichment(XFemModel model) {
		// TODO Auto-generated method stub
		
		ListIterator elements = model.getElementsList().listIterator();
		while(elements.hasNext()){
			XFEMElement element = (XFEMElement) elements.next();
			element.reinitializeStateOfElement();
		}
		
		//get the old tip element
		int k = this.getInteractedElements().size() - 1;
		XFEMElement currentTipElement = this.getInteractedElements().get(k);
		
		//update the crack tip's geometry
		CrackGrowthDirectionLaw dirLaw = model.getDirectionLaw(); 
		CrackGrowthIncrementLaw incLaw = model.getIncrementLaw();
		if((dirLaw instanceof NonLocalPrincipalStressDirection) && (incLaw instanceof BoundaryElementIncrement)){
			this.updateMyGeometry((NonLocalPrincipalStressDirection)dirLaw,(BoundaryElementIncrement)incLaw);
		}
		else
			this.updateMyGeometry(dirLaw,incLaw);
		
		// ***********************************************************************
		// *****            Update the geo-mesh interaction                   ****
		// ***********************************************************************
		
		if(this.isStillInTipElement()){
			currentTipElement.setStateOfElement();
			this.myCrackInterior.updateMyGeometry();
			this.setStillInTipElement(false);
		}
		else{
			// The tip left old tip element !!!
			
			// erase enrichment items of old tip-element before updating
			currentTipElement.eraseListOfEnrichmentItems();
			// erase enrichment items of old tip-element's nodes before updating
			for(int i=0; i<currentTipElement.getNbNodes(); i++){
				XFEMElementNode elmNode = (XFEMElementNode)currentTipElement.getIncidence().get(i);
				elmNode.eraseListOfEnrichmentItems();
			}
			List<XFEMElement> elems;
			if((dirLaw instanceof NonLocalPrincipalStressDirection) && (incLaw instanceof BoundaryElementIncrement)){
				elems = this.defineNewTipElements();
				if(elems.size() > 1)
					this.setOnNode(true);
				else
					this.setOnElementEdge(true);
			}
			else{
				elems = this.defineUpdatedElements();
			}
			
			// 1. FIND ELEMENTS INTERSECT WITH THE CRACK ADVANCE
			IPoint3d o = this.getTipSegment().getFirstPoint();
			IPoint3d v = this.getTipSegment().getVector();
			Line l = new Line(o,v);
			List<XFEMElement> newSplitElems = new ArrayList<XFEMElement>();
			ListIterator iterator = elems.listIterator();
			while(iterator.hasNext()){
				XFEMElement e = (XFEMElement) iterator.next();
				if(e.intersects(l))
					newSplitElems.add(e);
			}
			
			// 2. FIND NEW TIP-ELEMENT
			// 2.1. The tip falls within element
			XFEMElement newTipElem = null; 
			ListIterator ci = newSplitElems.listIterator();
			boolean found = false;
			while(ci.hasNext() && !found){
				XFEMElement e = (XFEMElement) ci.next();
				if(((Vertex)this.getMyGeometry()).interactsWith(e)){
					newTipElem = e;
					found = true;
				}
			}
			
			// 2.2 if the circle used to do the update is too small, i.e., 
			// the crack advance is small, the code maybe did not find out the new tip element yet.
			if(newTipElem == null){
				List<XFEMElement> supOfOldTipElem = currentTipElement.getMyNeighbors();
				ci = supOfOldTipElem.listIterator();
				while(ci.hasNext() && !found){
					XFEMElement e = (XFEMElement) ci.next();
					if(((Vertex)this.getMyGeometry()).interactsWith(e)){
						newTipElem = e;
						found = true;
					}
				}
			}
			
			// 2.3 Tip touches element edge or goes through node !!!
			// for some cases (new tip touches element edge or goes through a node), 
			// do not find out new tip element yet.
			if(newTipElem == null){
				if(this.isOnElementEdge == true){
					double x = this.getTip().getCoordinates(0);
					double y = this.getTip().getCoordinates(1);

					IPoint3d p = new IPoint3d( Math.floor(x*1e6+0.5)/1e6 , Math.floor(y*1e6+0.5)/1e6 );
					
					ListIterator i = newSplitElems.listIterator();
					found = false;
					while((!found) && (i.hasNext())){
						XFEMElement e = (XFEMElement) i.next();
						if(e.isOnEdge(p)){
							newTipElem = e;
							found = true;
						}
					}
				}
				else if(this.isOnNode == true){
					newTipElem = this.resolveConflictOnNode(newSplitElems,(NonLocalPrincipalStressDirection)dirLaw);
				}
				
				
//				List<XFEMElement> supportOfTipElem = newTipElem.getMyNeighbors();
//				ListIterator j = supportOfTipElem.listIterator();
//				found = false;
//				while((!found) && (j.hasNext())){
//					XFEMElement e = (XFEMElement) j.next();
//					if(e.isOnEdge(p)){
//						newTipElem = e;
//						found = true;
//					}
//				}
				
				if(newTipElem == null)
					assert(false);
			}
			
			// ***********************************************************************
			// *****                Update the enrichment                         ****
			// ***********************************************************************
			
			// 1. Enrich this new tip-element with the updated CrackTip
			newTipElem.isEnrichedWith(this);
			// the tip did not interact with old tip-element anymore.
			this.interactedElements.clear();
			this.setInteractedElements(newTipElem);
			// ATTENTION, FIXED ENRICHMENT AREA !!!
			newTipElem.setEnrichmentForMyNodes(this);
			
			// 2. Enrich new split-elements with the tip's associated CrackInterior
			ListIterator i = newSplitElems.listIterator();
			while(i.hasNext()){
				XFEMElement e = (XFEMElement) i.next();
				e.isEnrichedWith(this.getMyCrackInterior());
				this.myCrackInterior.setInteractedElements(e);
				e.setEnrichmentForMyNodes(this.getMyCrackInterior());
			}
			
			// ***********************************************************************
			// *****             Resolve conflicts in enrichment                  ****
			// ***********************************************************************
			if(!(model.getDirectionLaw() instanceof NonLocalPrincipalStressDirection)
					&& !(model.getIncrementLaw() instanceof BoundaryElementIncrement)){
				ListIterator nodes = newTipElem.getIncidence().listIterator();
				while(nodes.hasNext()){
					XFEMElementNode node = (XFEMElementNode) nodes.next();
					node.resolveConflictsInEnrichment();
				}
			}
			
			// ***********************************************************************
			// *****         Resolve linear dependency in enrichment              ****
			// ***********************************************************************
			// RESOLVE LINEAR DEPENDENCY FOR NEW STEP ENRICHED NODES ...
			XFEMElementNode aNode = null;
			List<XFEMElementNode> StepEnrichedNodes = new ArrayList<XFEMElementNode>();
			i = newSplitElems.listIterator();
			while(i.hasNext()){
				XFEMElement e = (XFEMElement) i.next();
				for(int j=0; j<e.getNbNodes(); j++){
					aNode = (XFEMElementNode) e.getIncidence().get(j);
					if(!StepEnrichedNodes.contains(aNode))
						StepEnrichedNodes.add(aNode);
				}
			}
			
			// Update geometry of associated CrackInterior
			this.myCrackInterior.updateMyGeometry();
			
//			ListIterator liK = StepEnrichedNodes.listIterator();
//			while(liK.hasNext()){
//				XFEMElementNode node = (XFEMElementNode) liK.next();
//				if(node.isStepEnriched()){
//					node.resolveLinearDependency(this.getMyCrackInterior());
//				}
//			}
			
			// Find out set of elements whose stiffness matrices need to be recomputed 
			// Also mark new enriched nodes so that the code can update DOFs...
			// Steps :
			//   1. Find nodes of elements intersecting with the  crack advance
			//   2. Find the nodal support of these newEnrichedNodes
			
			if(!newSplitElems.contains(newTipElem))
				newSplitElems.add(newTipElem);
			
			List<XFEMElementNode> newEnrichedNodes = new ArrayList<XFEMElementNode>();
			i = newSplitElems.listIterator();
			while(i.hasNext()){
				XFEMElement e = (XFEMElement) i.next();
				for(int j=0; j<e.getNbNodes(); j++){
					aNode = (XFEMElementNode) e.getIncidence().get(j);
					if(!newEnrichedNodes.contains(aNode))
						newEnrichedNodes.add(aNode);
				}
			}
			
			List<XFEMElement> updElements = new ArrayList<XFEMElement>();
			for(int j=0; j<newEnrichedNodes.size(); j++){
				XFEMElementNode n = newEnrichedNodes.get(j);
				updElements.addAll(n.getElementsThatIbelongTo());
			}
			
			// removing the redundancies in updated elements list
			Collection<XFEMElement> updtElements = new HashSet<XFEMElement>(updElements);
			List<XFEMElement> updatedElements = new ArrayList<XFEMElement>(updtElements);
			
			// 3. Mark these elements so that their stiffness matrices are recomputed
			ListIterator liUpElms = updatedElements.listIterator();
			while(liUpElms.hasNext()){
				XFEMElement e = (XFEMElement) liUpElms.next();
				e.setStateOfElement();
			}
			
			// 4. Mark new enriched nodes
			for(int j=0; j<newEnrichedNodes.size(); j++){
				if(((XFEMElementNode)newEnrichedNodes.get(j)).getIsEnriched() == 1){
					((XFEMElementNode)newEnrichedNodes.get(j)).setIsUpdated();
				}
			}
		}
	}
	
	/**
	 * Finds the new XFEMElement(s) that holds the updated crack tip.
	 * Used for crack advance computation with NonLocalPrincipalStressDirection
	 * and BoundaryElementIncrement class.
	 * @return A List with element(s) that hold the updated crack tip. 
	 */
	public List<XFEMElement> defineNewTipElements() {
		// TODO Auto-generated method stub
		List<XFEMElement> newTipElements = new ArrayList<XFEMElement>();
		IPoint3d updCrackTip = new IPoint3d(this.getTip().getCoordinates(0),this.getTip().getCoordinates(1));
		
		XFEMElement oldTipElement = this.getInteractedElements().get(this.getInteractedElements().size() - 1);
		ListIterator oldNeighbors = oldTipElement.getMyNeighbors().listIterator();
		while(oldNeighbors.hasNext()){
			XFEMElement actualNeighbor = (XFEMElement) oldNeighbors.next();
			
			List<IPoint3d> pts = new ArrayList<IPoint3d>();
			List<ElementNode> nodesList = actualNeighbor.getIncidence();
			ListIterator nodes = nodesList.listIterator();
			while(nodes.hasNext()){
				XFEMElementNode node = (XFEMElementNode) nodes.next();
				IPoint3d p = new IPoint3d(node.getX(),node.getY());
				pts.add(p);
			}
			
			boolean found = false;
			Segment s = new Segment();
			int i = 0;
			while(i<pts.size() && !found){
				if(i<(pts.size()-1)){
					s.setFirstPoint((IPoint3d)pts.get(i));
					s.setSecondPoint((IPoint3d)pts.get(i+1));
				}
				if(i == (pts.size()-1)){
					s.setFirstPoint((IPoint3d)pts.get(i));
					s.setSecondPoint((IPoint3d)pts.get(0));
				}
				double x = s.getSecondPoint().getX()-s.getFirstPoint().getX();
				double y = s.getSecondPoint().getY()-s.getFirstPoint().getY();
				s.setVector(new IPoint3d(x,y));
				
				if(s.on(updCrackTip)){
					newTipElements.add(actualNeighbor);
					found = true;
				}
				i++;
			}
		}
		return newTipElements;
	}

	/**
	 * 
	 * @return
	 */
	public List<XFEMElement> defineUpdatedElements(){
		return null;
	}
	
	/**
	 * Gets the tip of this CrackTip.
	 * @return The vertex associated with the tip of this CrackTip.
	 */
	public Vertex getTip(){
		return (Vertex)this.getMyGeometry();
	}

	/**
	 * Gets the boolean information if this crack tip is on an element edge or not. 
	 * If isOnElementEdge hold true, this crack tip is on element edge. If 
	 * isOnElementEdge hold false, this crack tip is inside an element or on a node.
	 * @return Returns the isOnElementEdge.
	 */
	public boolean isOnElementEdge() {
		return isOnElementEdge;
	}

	/**
	 * Sets the boolean information if this crack tip is on an element edge or not. 
	 * If isOnElementEdge hold true, this crack tip is on element edge. If 
	 * isOnElementEdge hold false, this crack tip is inside an element or on a node.
	 * @param isOnElementEdge The isOnElementEdge to set.
	 */
	public void setOnElementEdge(boolean isOnElementEdge) {
		this.isOnElementEdge = isOnElementEdge;
	}
	
	/**
	 * Finds next tip element when crack tip is on node that is shared with
	 * several elements. The direction of crack advance is computed for each
	 * eligible element and compared with previous direction. The elected
	 * element is one that its computed crack advance direction is compatible
	 * with previous crack advance direction.
	 * @param conflictElms The List holding conflicted elements.
	 * @param dirLaw The provided direction law for crack angle 
	 * computation.
	 * @return The selected element that will be splited by crack.
	 */
	public XFEMElement resolveConflictOnNode(List<XFEMElement> conflictElms, NonLocalPrincipalStressDirection dirLaw){
		IPoint3d currentCrackTipPoint = new IPoint3d(this.getTip().getCoordinates(0),this.getTip().getCoordinates(1));
		XFEMElement selectedElm = null;
		double majorAngle = 0.0;
		
		IPoint3d previousTgD = this.getTipSegment().getVector();
		double lengthPTgD = Math.sqrt( Math.pow(previousTgD.getX(),2.0) + Math.pow(previousTgD.getY(),2.0));
		IPoint3d previousTgDirection = new IPoint3d(previousTgD.getX()/lengthPTgD, previousTgD.getY()/lengthPTgD);
		double lengthPTgDir = Math.sqrt( Math.pow(previousTgDirection.getX(),2.0) + Math.pow(previousTgDirection.getY(),2.0));
		
		ListIterator cElms = conflictElms.listIterator();
		while(cElms.hasNext()){
			XFEMElement candidateElm = (XFEMElement) cElms.next();
			IVector currentNormalDirection = dirLaw.getDirection(candidateElm,currentCrackTipPoint);
			IPoint3d currentTgD = new IPoint3d(currentNormalDirection.getElement(1), -currentNormalDirection.getElement(0));
			double lengthCTgD = Math.sqrt( Math.pow(currentTgD.getX(),2.0) + Math.pow(currentTgD.getY(),2.0));
			IPoint3d currentTgDirection = new IPoint3d(currentTgD.getX()/lengthCTgD, currentTgD.getY()/lengthCTgD);
			double lengthCTgDir = Math.sqrt( Math.pow(currentTgDirection.getX(),2.0) + Math.pow(currentTgDirection.getY(),2.0));
			
			double dotProduct = currentTgDirection.getX()*previousTgDirection.getX() + currentTgDirection.getY()*previousTgDirection.getY();
			double angle = Math.acos(dotProduct/(lengthPTgDir * lengthCTgDir));
			
			if(angle > majorAngle){
				selectedElm = candidateElm;
				majorAngle = angle;
			}
		}
		return selectedElm;
	}

}
